using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Security;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using PS.Practices.SiteBuilder.Features.API;
using PS.Practices.SiteBuilder.Features.Schema;
using PS.Practices.SiteBuilder.Features.Schema.DOM;
using PS.Practices.Util;
using WebPart = PS.Practices.SiteBuilder.Features.Schema.DOM.WebPart;

namespace PS.Practices.Provisioner.Provisioner.Schema.Logic
{
    public class OmWebOperationVisitor : IWebOperationVisitor {

        private SPWeb web;
        private Dictionary<string, object> properties = new Dictionary<string, object>(); 

        public OmWebOperationVisitor(SPWeb web, Dictionary<string, object> properties )
        {
            this.web = web;
            this.properties = properties;
        }

        //TODO Make sure this is called by responsible parties
        public void Dispose()
        {
            this.web.Dispose();
            this.web = null;
        }

        #region Web Operations

        /// <summary>
        /// Performs tasks related to the ProvisionerRoot xml node
        /// </summary>
        /// <param name="provisionerRoot">Object which represents the ProvisionerRoot xml</param>
        /// 
        /// ApplyWebTemplate - Does not work with typical web creation methods for 2010 (OM and GUI).
        /// Possible (untested) options might be:  Use in MOSS, site collection provisioning at the farm level with deferred template selection
        /// 
        public void Visit(ProvisionerRoot provisionerRoot)
        {
            SPWeb priorWeb = null;
            try
            {
                if (!string.IsNullOrEmpty(provisionerRoot.ApplyWebTemplate))
                {
                    web.ApplyWebTemplate(provisionerRoot.ApplyWebTemplate);

                    if (provisionerRoot.ReopenWebOnApplyWebTemplate)
                    {
                        //SP2010 Publishing Feature fix. reopen the web after the call to ApplyWebTemplate to avoid an update conflict with entries 
                        //added by the Prov feature activation sink into the web's property bag 
                        priorWeb = web;
                        var webId = web.ID;
                        web = web.Site.OpenWeb(webId);
                    }
                }

                #region MasterUrl

                bool update = false;
                if (!string.IsNullOrEmpty(provisionerRoot.MasterUrl) &&
                    !provisionerRoot.MasterUrl.Equals(ProvisionerRoot.MasterPageActions.NoneAction, StringComparison.InvariantCultureIgnoreCase))
                {
                    update = true;
                    string masterPageUrl = provisionerRoot.MasterUrl;

                    if (provisionerRoot.MasterUrl.Equals(ProvisionerRoot.MasterPageActions.SetToRoot, StringComparison.InvariantCultureIgnoreCase))
                    {
                        using (SPWeb root = web.Site.OpenWeb(web.Site.ServerRelativeUrl))
                        {
                            masterPageUrl = root.MasterUrl;
                        }
                    }
                    else if (provisionerRoot.MasterUrl.Equals(ProvisionerRoot.MasterPageActions.SetToParent, StringComparison.InvariantCultureIgnoreCase))
                    {
                        using (SPWeb parentWeb = web.Site.OpenWeb(web.ParentWebId))
                        {
                            masterPageUrl = parentWeb.MasterUrl;
                        }
                    }
                    else
                    {

                        masterPageUrl = masterPageUrl;
                        web.CustomMasterUrl = masterPageUrl;

                    }

                    web.MasterUrl = masterPageUrl;

                }

                #endregion

                #region CustomMasterUrl

                if (!string.IsNullOrEmpty(provisionerRoot.CustomMasterUrl) &&
                    !provisionerRoot.CustomMasterUrl.Equals(ProvisionerRoot.MasterPageActions.NoneAction, StringComparison.InvariantCultureIgnoreCase))
                {
                    update = true;
                    string customMasterPageUrl = provisionerRoot.CustomMasterUrl;

                    if (provisionerRoot.CustomMasterUrl.Equals(ProvisionerRoot.MasterPageActions.SetToRoot, StringComparison.InvariantCultureIgnoreCase))
                    {
                        using (SPWeb root = web.Site.OpenWeb(web.Site.ServerRelativeUrl))
                        {
                            customMasterPageUrl = root.CustomMasterUrl;
                        }
                    }
                    else if (provisionerRoot.CustomMasterUrl.Equals(ProvisionerRoot.MasterPageActions.SetToParent,
                                                    StringComparison.InvariantCultureIgnoreCase))
                    {
                        using (SPWeb parentWeb = web.Site.OpenWeb(web.ParentWebId))
                        {
                            customMasterPageUrl = parentWeb.CustomMasterUrl;
                        }
                    }

                    web.CustomMasterUrl = customMasterPageUrl;
                }

                if (update)
                    web.Update();

                #endregion
            }
            finally
            {
                if (priorWeb != null && web != null)
                {
                    web.Dispose();
                    web = priorWeb; //restore original web for the callers.. the upstream code should not be calling web.Update() as this will result in the update conflict
                }
            }
        }

        public void Visit(RestrictSiteTemplates action)
        {
#if SANDBOX
            Logger.Info("'RestrictSiteTemplates' operation is not available within the sandbox");
            return;
#endif
            var coll = new Dictionary<string, uint>();
            foreach (var template in action.Templates)
            {
                uint lcid = web.Language;
                if (!string.IsNullOrEmpty(template.LCID))
                {
                    uint.TryParse(template.LCID, out lcid);
                }
                coll.Add(template.Value, lcid);
            }

            WebUtil.RestrictSiteTemplates(web, coll);
            web.Update();
        }

        public void Visit(WebProperies action)
        {
            foreach (WebProperty webProperty in action.Properties)
            {
                if (!string.IsNullOrEmpty(webProperty.Key) && !string.IsNullOrEmpty(webProperty.Value))
                {
                    string key = webProperty.Key;
                    string value = webProperty.Value;
#if !SANDBOX
                    if (web.Properties.ContainsKey(key))
                    {
                        web.Properties[key] = value;
                    }
                    else
                    {
                        web.Properties.Add(key, value);
                    }
#else
                    if (web.AllProperties.ContainsKey(key))
                    {
                        Logger.Info("Updating Property: Key: " + key + ", Value: " + value);
                        web.AllProperties[key] = value;
                    }
                    else
                    {
                        Logger.Info("Adding Property: Key: " + key + ", Value: " + value);
                        web.AddProperty(key, value);
                    }
#endif
                }
            }

#if !SANDBOX
            web.Properties.Update();
#else
            web.AllowUnsafeUpdates = true;
            web.Update();
            web.AllowUnsafeUpdates = false;
#endif
        }

        public void Visit(FixupWebACL fixupWebAcl)
        {
            PSWebAcl webAcl = new PSWebAcl(web);

            // Logger.Info("Configuring web ACL");

            webAcl.ChangeInheritTopNav(fixupWebAcl.InheritTopNav);

            if (fixupWebAcl.RespectInheritanceSetting && !(web.HasUniqueRoleDefinitions || web.HasUniqueRoleAssignments))
            {
                Logger.Info("RespectInheritanceSetting setting is set or assumed as TRUE and the web does not seem to have unique permissions specified. the FixupWebACL actions shall be skipped.");
                return;
            }

            webAcl.ChangeRoleAssignments(fixupWebAcl.RoleAssignments);

            OmWebOperationVisitorHelpers.ProcessQuickLaunch(fixupWebAcl, web);

            web.Update();
        }

        public void Visit(SetStyling setStyling)
        {
            if (!string.IsNullOrEmpty(setStyling.ApplyTheme))
            {
                web.ApplyTheme(setStyling.ApplyTheme);
            }
            if (!string.IsNullOrEmpty(setStyling.AlternateCss))
            {
                web.AlternateCssUrl = setStyling.AlternateCss;
            }
            if (!string.IsNullOrEmpty(setStyling.SiteLogoUrl))
            {
                web.SiteLogoUrl = setStyling.SiteLogoUrl;
            }

            web.Update();
        }

        public void Visit(WebFeatures webFeatures)
        {
            using (SPSite site = new SPSite(web.Url))
            {
                using (SPWeb curWeb = site.OpenWeb())
                {
                    foreach (string feature in webFeatures.Features)
                    {
                        Guid ftrGuid = new Guid(feature.Trim());
                        SPFeature ftr = curWeb.Features[ftrGuid];

                        if (ftr == null)
                        {
                            curWeb.Features.Add(ftrGuid);
                        }
                    }

                    curWeb.AllowUnsafeUpdates = true;
                    curWeb.Update();
                    curWeb.AllowUnsafeUpdates = false;
                }
            }
        }

        public void Visit(User user)
        {
            if (string.IsNullOrEmpty(user.DisplayName))
                user.DisplayName = user.LoginName;

            PSWebAcl webAcl = new PSWebAcl(web);

            switch (user.Action)
            {
                case "Add":
                    webAcl.AddWebUser(user.LoginName, user.DisplayName, user.Email, user.Description);
                    break;
                case "Remove":
                    webAcl.RemoveWebUser(user.LoginName);
                    break;
                default:
                    Logger.Info("Unrecognized action: " + user.Action);
                    break;
            }
        }

        public void Visit(SiteGroup siteGroup)
        {
            switch (siteGroup.Action)
            {
                case "Create":
                    OmWebOperationVisitorHelpers.CreateGroup(siteGroup, web);
                    break;
                case "Delete":
                    Logger.Info("Deleting cross site group: " + siteGroup.Name);
                    AclUtil.DeleteWebGroup(siteGroup.Name, web);
                    break;
                default:
                    Logger.Info("Unrecognized action: " + siteGroup.Action);
                    break;
            }
        }

        public void Visit(PrincipalRole principalRole)
        {
            bool doAdd = principalRole.Action == "Add";
            bool doRemove = principalRole.Action == "Remove";

            if (!doAdd && !doRemove)
            {
                Logger.Info("Unsupported action attribute: " + principalRole.Action);
                return;
            }

            PSAcl acl = new PSAcl(web, web);

            switch (principalRole.Type)
            {
                case "SiteGroup":
                    {
                        if (doAdd)
                            acl.AddSiteGroup(principalRole.Name, principalRole.Role);
                        else
                            acl.RemoveSiteGroup(principalRole.Name, principalRole.Role);

                        break;
                    }

                case "User":
                    {
                        if (doAdd)
                            acl.AddUser(principalRole.Name, principalRole.Role);
                        else
                            acl.RemoveUser(principalRole.Name, principalRole.Role);

                        break;
                    }

                case "FbaRole":
                    {
                        if (doAdd)
                            acl.AddFbaRole(principalRole.Name, principalRole.Role);
                        else
                            acl.RemoveFbaRole(principalRole.Name, principalRole.Role);

                        break;
                    }

                default:
                    Logger.Info("Unsupported principal type : " + principalRole.Type);
                    break;
            }
        }

        public void Visit(File file)
        {
            SPFile spFile = web.GetFile(file.Path);

            SPListItem item = null;
            try
            {
                item = spFile.Item;
            }
            catch (SPException)
            {
            }
#if MOSS
            PublishingPage page = null;

            if (item != null)
            {
                if (PublishingPage.IsPublishingPage(item))
                {
                    page = PublishingPage.GetPublishingPage(item);
                    if (page.ListItem.File.CheckOutStatus == SPFile.SPCheckOutStatus.None)
                    {
                        page.CheckOut();
                        page.Update();
                    }
                }

                else
                {
                    try
                    {
                        spFile.CheckOut();
                        spFile.Update();
                    }
                    catch (SPException)
                    {
                    }
                }
            }
#else

            try
            {
                spFile.CheckOut();
                spFile.Update();
            }
            catch (SPException)
            {
            }

#endif

            using (SPLimitedWebPartManager manager =
                spFile.GetLimitedWebPartManager(PersonalizationScope.Shared))
            {
                SPLimitedWebPartCollection pageWebParts = manager.WebParts;
                if (pageWebParts == null)
                {
#if MOSS
                    if (page != null)
                        page.CheckIn("");
#endif
                    return;
                }

                foreach (WebPart webPart in file.WebParts)
                {
                    System.Web.UI.WebControls.WebParts.WebPart pageWebPart;
                    if (OmWebOperationVisitorHelpers.FindWebPart(pageWebParts, webPart, out pageWebPart))
                    {
                        bool saveChanges = webPart.Process(pageWebPart, web, manager);
                        if (saveChanges)
                        {
                            try
                            {
                                manager.SaveChanges(pageWebPart);
                            }
                            catch (WebPartPageUserException)
                            {
                            }
                        }
                    }
                }

                foreach (DataViewsZone viewsZone in file.DataViewsZone)
                {
                    viewsZone.Process(web, spFile);
                }
            }

            const string txt = "Post provisioning modifications";
#if MOSS
            if (page != null)
            {
                string comment = txt;
                page.CheckIn(comment);
                spFile.Publish(comment);

                try
                {
                    spFile.Approve(comment);
                }
                catch (SPException)
                {
                }
            }
            else
#endif
            {
                if (item != null)
                {
                    try
                    {
                        spFile.CheckIn(txt);
                        spFile.Publish(txt);
                        spFile.Approve(txt);
                    }
                    catch (SPException)
                    {
                    }
                }
            }
        }

        public void Visit(FbaRole fbaRole)
        {
            switch (fbaRole.Action)
            {
                case "Create":
                    Roles.Provider.CreateRole(ParserUtil.ReplaceLiveFields(fbaRole.Name, web));
                    break;
                case "Delete":
                    Logger.Info("Deleting cross site group: " + fbaRole.Name);
                    Roles.Provider.DeleteRole(ParserUtil.ReplaceLiveFields(fbaRole.Name, web), true);
                    break;
                default:
                    Logger.Info("Unrecognized action: " + fbaRole.Action);
                    break;
            }
        }

        public void Visit(AssociateSiteGroup associateSiteGroup)
        {
            if (string.IsNullOrEmpty(associateSiteGroup.Role) || string.IsNullOrEmpty(associateSiteGroup.Name))
                return;

            PSWebAcl webAcl = new PSWebAcl(web);
            this.web = web;

            webAcl.AssociateSiteGroup(associateSiteGroup.Name, associateSiteGroup.Role);
        }

        public void Visit(ProvisionWebs provisionWebs)
        {
            var nodes = provisionWebs.Webs;
            OmWebOperationVisitorHelpers.ProvisionWebs(nodes, web, properties);
        }

        public void Visit(CustomizedCssFiles customizedCssFiles)
        {
            List<string> customizedCssFilesStr = new List<string>();
            foreach (CustomizedCssFile cssFile in customizedCssFiles.Files)
            {
                if (!string.IsNullOrEmpty(cssFile.Name))
                {
                    customizedCssFilesStr.Add(cssFile.Name);
                }
            }

            SPFolder rootFolder = web.RootFolder;
            SPFolder cssFolder = null;
            string targetFolder = "_styles";
            foreach (SPFolder subFolder in rootFolder.SubFolders)
            {
                if (subFolder.Name.ToLower(CultureInfo.InvariantCulture) == targetFolder)
                {
                    cssFolder = subFolder;
                    break;
                }
            }

            if (cssFolder == null)
            {
                return;
            }

            foreach (SPFile file in cssFolder.Files)
            {
                if (customizedCssFilesStr.Contains(file.Name))
                {
                    web.CustomizeCss(file.Name);
                    web.Update();
                }
            }
        }

        #endregion Web Operations

        //TODO - Remove call to listNode.Accept
        public void Visit(ProvisionLists provisionLists)
        {
            foreach (List listNode in provisionLists.Lists)
            {
                if (string.IsNullOrEmpty(listNode.Title))
                {
                    Logger.Info("Required attribute Title is missing");
                    continue;
                }

                if (string.IsNullOrEmpty(listNode.Template.ToString()))
                {
                    Logger.Info("Required attribute Template is missing");
                    continue;
                }

                SPList newList = web.Lists.TryGetList(listNode.Title);
                if (newList != null)
                {
                    Logger.Info("List already exists, cannot provision");
                    continue;
                }

                web.AllowUnsafeUpdates = true;
                Guid newListId = web.Lists.Add(listNode.Title, listNode.Description, (SPListTemplateType)listNode.Template);
                web.AllowUnsafeUpdates = false;

                newList = web.Lists.GetList(newListId, false);
                listNode.Accept(new OmListOperationVisitor(newList, web, properties));
            }
        }

        public void Visit(FixupLists fixupLists)
        {
            if (fixupLists.SyncCTypeLocalFieldSettings)
            {
                try
                {
                    ListUtil.SyncReqHidFieldStatusInLists(web);
                }
                catch (Exception e)
                {
                    Logger.Error(e);
                }
            }

            foreach (List list in fixupLists.Lists)
            {
                if (!string.IsNullOrEmpty(list.Title))
                {
                    SPList spList = null;
                    try
                    {
                        spList = web.Lists[list.Title];
                    }
                    catch (IndexOutOfRangeException)
                    { }
                    catch (ArgumentException)
                    { }

                    if (spList != null)
                    {
                        OmWebOperationVisitorHelpers.ProcessList(new OmListOperationVisitor(spList, web, properties), list, spList);
                    }
                }
            }
        }

        public void Visit(AddMembersToSiteGroup addMembersToSiteGroup)
        {
                object users = this.ReplaceLiveFields(addMembersToSiteGroup.Members);
                string groupName = ReplaceLiveFields(addMembersToSiteGroup.GroupName).ToString();

                if (users is SPFieldUserValueCollection)
                {
                    SPFieldUserValueCollection spfuvc = users as SPFieldUserValueCollection;

                    foreach (SPFieldUserValue user in spfuvc)
                    {
                        AclUtil.AddToSecurityGroup(groupName, user.User, web);
                    }

                    users = users as SPFieldUserValueCollection;
                }
                else if (users is SPFieldUserValue)
                {
                    SPFieldUserValue spfuv = users as SPFieldUserValue;
                    AclUtil.AddToSecurityGroup(groupName, spfuv.User, web);
                }
                else if (users is string)
                {
                    string commaSeparatedUsers = users as string;
                    string[] usersArray = commaSeparatedUsers.Split(',');

                    foreach(string userLogonName in usersArray)
                    {
                        SPUser user = web.EnsureUser(userLogonName);
                        AclUtil.AddToSecurityGroup(groupName, user, web);
                    }
                }
        }

        //TODO Refactor live fields to support Properties in a better way
        public object ReplaceLiveFields(string val)
        {
            if (val == null) throw new ArgumentNullException("val");
            string v = val;
            object ret = new object();

            if (v.Contains("{{Property"))
            {
                ret = ParserUtil.ReplacePropertiesField(v, properties);
            }
            else
            {
                ret = ParserUtil.ReplaceLiveFields(v, web);
            }

            return ret;

        }
    }
}